﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Resources;
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio.CommandBars;
using Neovolve.Extensibility.VisualStudio.Properties;

namespace Neovolve.Extensibility.VisualStudio
{
    public abstract class ConnectBase : IDTExtensibility2, IDTCommandTarget
    {
        /// <summary>Called when the command is invoked.</summary>
        /// <param term='commandName'>The name of the command to execute.</param>
        /// <param term='executeOption'>Describes how the command should be run.</param>
        /// <param term='varIn'>Parameters passed from the caller to the command handler.</param>
        /// <param term='varOut'>Parameters passed from the command handler to the caller.</param>
        /// <param term='handled'>Informs the caller if the command was handled or not.</param>
        /// <seealso class='Exec' />
        public virtual void Exec(
            string cmdName,
            vsCommandExecOption executeOption,
            ref object cariantIn,
            ref object cariantOut,
            ref bool handled)
        {
            handled = false;
        }

        /// <summary>Receives notification when the collection of Add-ins has changed.</summary>
        /// <param term='custom'>Array of parameters that are host application specific.</param>
        /// <seealso class='IDTExtensibility2' />
        public virtual void OnAddInsUpdate(ref Array custom)
        {
        }

        /// <summary>Receives notification that the host application is being unloaded.</summary>
        /// <param term='custom'>Array of parameters that are host application specific.</param>
        /// <seealso class='IDTExtensibility2' />
        public virtual void OnBeginShutdown(ref Array custom)
        {
        }

        /// <summary>Receives notification that the Add-in is being loaded.</summary>
        /// <param term='application'>Root object of the host application.</param>
        /// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
        /// <param term='addInInst'>Object representing this Add-in.</param>
        /// <seealso class='IDTExtensibility2' />
        public virtual void OnConnection(
            object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
        {
            ApplicationObject = (DTE2)application;
            AddInInstance = (AddIn)addInInst;

            CheckBuildMode();
        }

        /// <summary>Receives notification that the Add-in is being unloaded.</summary>
        /// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
        /// <param term='custom'>Array of parameters that are host application specific.</param>
        /// <seealso class='IDTExtensibility2' />
        public virtual void OnDisconnection(ext_DisconnectMode removeMode, ref Array custom)
        {
        }

        /// <summary>Receives notification that the host application has completed loading.</summary>
        /// <param term='custom'>Array of parameters that are host application specific.</param>
        /// <seealso class='IDTExtensibility2' />
        public virtual void OnStartupComplete(ref Array custom)
        {
        }

        /// <summary>This is called when the command's availability is updated</summary>
        /// <param term='commandName'>The name of the command to determine state for.</param>
        /// <param term='neededText'>Text that is needed for the command.</param>
        /// <param term='status'>The state of the command in the user interface.</param>
        /// <param term='commandText'>Text requested by the neededText parameter.</param>
        /// <seealso class='Exec' />
        public virtual void QueryStatus(
            string cmdName,
            vsCommandStatusTextWanted neededText,
            ref vsCommandStatus statusOption,
            ref object commandText)
        {
        }

        /// <summary>
        /// Checks if IDE is in build mode.
        /// </summary>
        protected void CheckBuildMode()
        {
            String commandLineArgs = ApplicationObject.CommandLineArguments.ToLower(CultureInfo.InvariantCulture);
            BuildMode = (commandLineArgs.IndexOf(Resources.BuildModeCommandSwitch, StringComparison.OrdinalIgnoreCase)
                         != -1);
        }

        /// <summary>
        /// Gets the command.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <returns></returns>
        protected Command GetCommand(String commandName)
        {
            Command addinCommand;

            try
            {
                String fullCommand = GetFullCommandName(commandName);

                addinCommand = ApplicationCommands.Item(fullCommand, -1);
            }
            catch
            {
                addinCommand = null;
            }

            return addinCommand;
        }

        /// <summary>
        /// Gets the full name of the command.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <returns></returns>
        protected String GetFullCommandName(String commandName)
        {
            const String CommandFormat = "{0}.{1}";

            return String.Format(CultureInfo.InvariantCulture, CommandFormat, AddInInstance.ProgID, commandName);
        }

        /// <summary>
        /// Retreives localized menu name from the embedded resource.
        /// </summary>
        protected string GetLocalizedMenuName(String menuName)
        {
            try
            {
                ResourceManager resourceManager = new ResourceManager(
                    typeof(Resources).FullName, Assembly.GetExecutingAssembly());
                CultureInfo culture = new CultureInfo(ApplicationObject.LocaleID);
                String resourceName = menuName.Replace(" ", String.Empty);

                return resourceManager.GetString(resourceName, culture);
            }
            catch
            {
                // Do nothing
                Debug.WriteLine("The menu '{0}' was not found.", menuName);
            }

            return menuName;
        }

        /// <summary>
        /// Gets the command bar.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        protected CommandBar GetCommandBar(String name)
        {
            CommandBars applicationBars = ApplicationObject.CommandBars as CommandBars;

            if (applicationBars == null)
            {
                throw new MenuCommandException(Resources.CommandBarsNotAvailable);
            }

            CommandBar bar = applicationBars[name];

            if (bar == null)
            {
                throw new MenuCommandException(String.Format(CultureInfo.CurrentCulture, Resources.CommandBarNotFoundFormat, name));
            }

            return bar;
        }

        protected CommandBarPopup GetMenuPopup(String menuName, CommandBar menuBar)
        {
            CommandBarControl viewControl = GetMenuItem(menuName, menuBar);

            CommandBarPopup viewPopup = viewControl as CommandBarPopup;

            if (viewPopup == null)
            {
                throw new MenuCommandException(Resources.MenuPopupNotFound);
            }

            return viewPopup;
        }

        private CommandBarControl GetMenuItem(string menuName, CommandBar menuBar)
        {
            String localizedMenuName = GetLocalizedMenuName(menuName);

            CommandBarControl viewControl = menuBar.Controls[localizedMenuName];

            if (viewControl == null)
            {
                throw new MenuCommandException(Resources.MenuNotAvailableFormat, localizedMenuName);
            }

            return viewControl;
        }

        protected AddIn AddInInstance
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the application commands.
        /// </summary>
        /// <value>The application commands.</value>
        protected Commands2 ApplicationCommands
        {
            get
            {
                if (ApplicationObject.Commands == null)
                {
                    throw new MenuCommandException(Resources.ApplicationCommandsNotAvailable);
                }

                return ApplicationObject.Commands as Commands2;
            }
        }

        protected DTE2 ApplicationObject
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether [build mode].
        /// </summary>
        /// <value><c>true</c> if [build mode]; otherwise, <c>false</c>.</value>
        protected Boolean BuildMode
        {
            get;
            set;
        }

        /// <summary>
        /// Creates the commandName.
        /// </summary>
        /// <param name="commandName">Name of the command.</param>
        /// <param name="buttonText">The button text.</param>
        /// <param name="tooltipText">The tooltip text.</param>
        /// <param name="resourceId">The resource id.</param>
        /// <returns></returns>
        protected Command CreateCommand(String commandName, String buttonText, String tooltipText, Int32 resourceId)
        {
            Command addinCommand = GetCommand(commandName);

            // Don't add the commandName if it's already there
            if (addinCommand == null)
            {
                Object[] contextGUIDS = new Object[] { };

                addinCommand = ApplicationCommands.AddNamedCommand2(
                    AddInInstance,
                    commandName,
                    buttonText,
                    tooltipText,
                    false,
                    resourceId,
                    ref contextGUIDS,
                    (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
                    (int)vsCommandStyle.vsCommandStylePictAndText,
                    vsCommandControlType.vsCommandControlTypeButton);

                if (addinCommand == null)
                {
                    throw new MenuCommandException(Resources.AddCommandFailed);
                }
            }

            return addinCommand;
        }
    }
}